/* Global declarations for the khipu API.

   This file is part of khipu.

   khipu is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

#ifndef __KHIPU__
#define __KHIPU__   1

#include "integer.hpp"
#include "floatp.hpp"
#include "memory.hpp"
#include "cons.hpp"
#include "symbol.hpp"
#include "stream.hpp"
#include "array.hpp"
#include "str.hpp"
#include "table.hpp"
#include "thread.hpp"
#include "tuple.hpp"
#include "function.hpp"
#include "io.hpp"
#include "xtime.hpp"
#include "coro.hpp"
#include "bytecode.hpp"
#include "builtins.hpp"
#include "event.hpp"
#include "types.hpp"
#include "custom.hpp"

KP_DECLS_BEGIN

KP_EXPORT bool khipu_init (char *base, uint32_t size);

template <typename T, bool B>
struct objtype_helper
{
  static void* unmask_ptr (object obj)
    {
      return (unmask (obj));
    }

  static bool check_type (object)
    {
      return (true);
    }
};

template <typename T>
struct objtype_helper<T, true>
{
  static custom_base* unmask_ptr (object obj)
    {
      return (as_custom (obj));
    }

  static bool check_type (object obj)
    {
      return (T::type_p (obj));
    }
};

template <typename T>
struct objtype
{
  typedef T* ret_type;
  static const int code = T::code;

  static inline ret_type get (object obj, int tp, bool& got)
    {
      if (tp == code || (code == typecode::BVECTOR && tp == typecode::STR))
        return ((ret_type)objtype_helper<
            T, code == typecode::CUSTOM>::unmask_ptr (obj));
      else if (tp == typecode::INSTANCE)
        {
          object tmp = builtin_member (obj);
          return (objtype<T>::get (tmp, itype (tmp), got));
        }

      got = false;
      return (nullptr);
    }
};

template <typename T>
inline T objtype_get_num (object obj, int tp, bool& got)
{
  switch (tp)
    {
      case typecode::INT:
        return ((T)as_int (obj));
      case typecode::CHAR:
        return ((T)as_char (obj));
      case typecode::FLOAT:
        return ((T)as_float (obj));
      default:
        got = false;
        return (T ());
    }
}

#define OBJTYPE_NUM(typ)   \
  template <>   \
  struct objtype<typ>   \
    {   \
      typedef typ ret_type;   \
      static const int code = typecode::INT;   \
      \
      static inline ret_type get (object obj, int tp, bool& got)   \
        {   \
          return (objtype_get_num<typ> (obj, tp, got));   \
        }   \
    }

OBJTYPE_NUM (int16_t);
OBJTYPE_NUM (uint16_t);
OBJTYPE_NUM (int32_t);
OBJTYPE_NUM (uint32_t);

OBJTYPE_NUM (float);
OBJTYPE_NUM (double);

#undef OBJTYPE_NUM

#define OBJTYPE_NUM(typ)   \
  template <>   \
  struct objtype<typ>   \
    {   \
      typedef typ ret_type;   \
      static const int code = typecode::INT;   \
      \
      static inline ret_type get (object obj, int tp, bool& got)   \
        {   \
          if (tp == typecode::BIGINT)   \
            {   \
              ret_type tmp;   \
              got = bigint::cast (obj, tmp);   \
              return (tmp);   \
            }   \
          \
          return (objtype_get_num<typ> (obj, tp, got));   \
        }   \
    }

OBJTYPE_NUM (int64_t);
OBJTYPE_NUM (uint64_t);

#undef OBJTYPE_NUM

template <>
struct objtype<char>
{
  typedef uint32_t ret_type;
  static const int code = typecode::CHAR;

  static inline ret_type get (object obj, int tp, bool& got)
    {
      if (tp == typecode::CHAR)
        return (as_char (obj));
      else if (tp == typecode::INT)
        return (as_int (obj));

      got = false;
      return (0);
    }
};

template <typename T>
bool as (object obj, typename objtype<T>::ret_type& out)
{
  bool rv = true;
  out = objtype<T>::get (obj, itype (obj), rv);
  return (rv);
}

template <typename T>
typename objtype<T>::ret_type as (object obj)
{
  typename objtype<T>::ret_type ret = 0;
  (void)as<T> (obj, ret);
  return (ret);
}

KP_DECLS_END

#endif
